package com.zy.explore.db.infrastructure.util;

import com.zy.explore.db.interfaces.vo.PageVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.client.solrj.response.UpdateResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 建类说明：solr工具类
 *
 * 使用此工具类，需要pom引入以下依赖
 *      <dependency>
 *          <groupId>org.springframework.boot</groupId>
 *          <artifactId>spring-boot-starter-data-solr</artifactId>
 *      </dependency>
 *
 * 同时application.properties中增加以下配置
 *      # solr配置，不加默认为此配置
 *      spring.data.solr.host=http://localhost:8983/solr/
 *      # 指定solr core，不指定默认core为 test_core
 *      spring.data.solr.core=test_core
 *
 * @author 中国印
 * @date 2022年02月13日 01:51:02
 */
@Slf4j
@Component
@ConditionalOnProperty("spring.data.solr")
@SuppressWarnings("unused")
public class SolrUtil {

    /**
     * 排序类型
     */
    public static final String DESC = SolrQuery.ORDER.desc.name();
    public static final String ASC = SolrQuery.ORDER.asc.name();

    /**
     * 初始页
     */
    private static final int MIN_PAGE_NUM = 1;

    /**
     * 页面最大值
     */
    private static final int MAX_PAGE_SIZE = 10000;
    /**
     * 页面最小值
     */
    private static final int MIN_PAGE_SIZE = 10;

    /**
     * 文档主键值
     */
    private static final String SOLR_ID = "id";

    private static final String SOLR_URL;

    private static final String SOLR_CORE;

    private static final SolrClient SOLR_CLIENT;

    static {
        SOLR_URL = PropertiesUtil.getProperty("spring.data.solr.host", "http://localhost:8983/solr/");
        SOLR_CORE = PropertiesUtil.getProperty("spring.data.solr.core", "test_core");
        SOLR_CLIENT = new HttpSolrClient.Builder(SOLR_URL).withConnectionTimeout(10000).withSocketTimeout(60000).build();
    }

    private SolrUtil() {}

    /**
     * 功能描述：新增索引(新增和修改)
     *
     * @param obj solr对象
     */
    public static boolean addBean(Object obj) {
        return addBean(obj, null);
    }

    /**
     * 功能描述：新增索引(新增和修改)
     *
     * @param obj      solr对象
     * @param coreName 存储集合名称，若为空，则存储集合为默认集合
     */
    public static boolean addBean(Object obj, String coreName) {
        if (obj == null) {
            return false;
        }
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        try {
            SOLR_CLIENT.addBean(coreName, obj);
            SOLR_CLIENT.commit(coreName);
            return true;
        } catch (Exception e) {
            log.error("创建对象索引异常：", e);
        }
        return false;
    }

    /**
     * 批量新增索引
     *
     * @param list 新增的对象集合
     */
    public static boolean addBeans(List<Object> list) {
        return addBeans(list, null);
    }

    /**
     * 批量新增索引
     *
     * @param list     新增的对象集合
     * @param coreName 目标集合，如传空，则使用默认集合
     */
    public static boolean addBeans(List<Object> list, String coreName) {
        if (CollectionUtils.isEmpty(list)) {
            log.error("集合为空");
            return false;
        }
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        try {
            SOLR_CLIENT.addBeans(coreName, list);
            SOLR_CLIENT.commit(coreName);
            return true;
        } catch (SolrServerException | IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 根据id批量删除指定索引
     *
     * @param ids      主键集合
     */
    public static boolean deleteByIds(List<String> ids) {
        return deleteByIds(ids, null);
    }

    /**
     * 根据id批量删除指定索引
     *
     * @param ids      主键集合
     * @param coreName 目标存储集合名称
     */
    public static boolean deleteByIds(List<String> ids, String coreName) {
        if (CollectionUtils.isEmpty(ids)) {
            return false;
        }
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        try {
            UpdateResponse response = SOLR_CLIENT.deleteById(coreName, ids);
            int status = response.getStatus();
            SOLR_CLIENT.commit(coreName);
            return status == 0;
        } catch (SolrServerException | IOException e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * 根据query批量删除关联索引
     *
     * @param query    查询字符串
     */
    public static boolean deleteByQuery(String query) {
        return deleteByQuery(query, null);
    }
    
    /**
     * 根据query批量删除关联索引
     *
     * @param query    查询字符串
     * @param coreName 目标存储集合名称
     */
    public static boolean deleteByQuery(String query, String coreName) {
        if (StringUtils.isBlank(query)) {
            return false;
        }
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        try {
            UpdateResponse response = SOLR_CLIENT.deleteByQuery(coreName, query);
            int status = response.getStatus();
            SOLR_CLIENT.commit(coreName);
            return status == 0;
        } catch (SolrServerException | IOException e) {
            log.error(e.getMessage(), e);
        }
        return false;
    }

    /**
     * 方法说明：删除所有索引
     *
     * @return boolean
     */
    public static boolean deleteAll() {
        return deleteAll(null);
    }

    /**
     * 方法说明：删除所有索引
     *
     * @param coreName 存储集合名称
     * @return boolean
     */
    public static boolean deleteAll(String coreName) {
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        try {
            UpdateResponse resp = SOLR_CLIENT.deleteByQuery(coreName, "*:*");
            int status = resp.getStatus();
            SOLR_CLIENT.commit(coreName);
            return status == 0;
        } catch (SolrServerException | IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 方法说明：solr检索查询
     *
     * @param query    查询
     * @param entity   实体
     * @param sort     排序
     * @param order    订单
     * @param offset   偏移量
     * @param row      行数
     * @return {@link List<Object> }
     * @throws Exception 异常
     */
    public static List<Object> search(String query, Class<?> entity, String sort, String order, int offset, int row) throws Exception {
        return search(null, query, entity, sort, order, offset, row);
    }

    /**
     * 方法说明：solr检索查询
     *
     * @param coreName 存储集合名称
     * @param query    查询
     * @param entity   实体
     * @param sort     排序
     * @param order    订单
     * @param offset   偏移量
     * @param row      行数
     * @return {@link List<Object> }
     * @throws Exception 异常
     */
    public static List<Object> search(String coreName, String query, Class<?> entity, String sort, String order, int offset, int row) throws Exception {
        List<Object> list = new ArrayList<>();
        // 构建查询对象
        SolrQuery solrQuery = buildQuery(query, entity);
        // 设置排序
        setSort(sort, order, solrQuery);
        // 设置查询页码和记录数
        if (offset < 0) {
            offset = 0;
        } else if (offset > MAX_PAGE_SIZE) {
            offset = MAX_PAGE_SIZE;
        }
        solrQuery.setStart(offset);
        solrQuery.setRows(Math.max(row, 0));
        // solr 检索
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        QueryResponse response = SOLR_CLIENT.query(coreName, solrQuery);
        SolrDocumentList docs = response.getResults();
        // 构建并返回结果集
        return buildSolrResultList(entity, list, docs);
    }



    /**
     * 方法说明：分页搜索
     *
     * @param query    查询
     * @param entity   实体
     * @param sort     排序
     * @param order    顺序
     * @param pageNum  页码
     * @param pageSize 页面大小
     * @return {@link PageVO<Object> }
     * @throws Exception 异常
     */
    public static PageVO<Object> searchPage(String query, Class<?> entity, String sort, String order, int pageNum, int pageSize) throws Exception {
        return searchPage(null, query, entity, sort, order, pageNum, pageSize);
    }


    /**
     * 方法说明：分页搜索
     *
     * @param coreName 存储集合名称
     * @param query    查询
     * @param entity   实体
     * @param sort     排序
     * @param order    顺序
     * @param pageNum  页码
     * @param pageSize 页面大小
     * @return {@link PageVO<Object> }
     * @throws Exception 异常
     */
    public static PageVO<Object> searchPage(String coreName, String query, Class<?> entity, String sort, String order, int pageNum, int pageSize) throws Exception {
        List<Object> list = new ArrayList<>();
        // 构建查询参数
        SolrQuery solrQuery = buildQuery(query, entity);
        // 设置排序
        setSort(sort, order, solrQuery);
        // 设置查询页码和记录数
        if (pageNum <= 0) {
            pageNum = MIN_PAGE_NUM;
        }
        if (pageSize > MAX_PAGE_SIZE) {
            pageSize = MAX_PAGE_SIZE;
        } else if (pageSize <= 0) {
            pageSize = MIN_PAGE_SIZE;
        }
        solrQuery.setStart((pageNum - 1) * pageNum);
        solrQuery.setRows(pageSize);

        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        // solr 检索
        SolrDocumentList docs = doSearch(coreName, solrQuery);
        // 构建结果集
        buildSolrResultList(entity, list, docs);
        return PageVO.of(pageNum, pageSize, (int) docs.getNumFound(), list);
    }

    /**
     * 方法说明：solr 高亮分页查询
     *
     * @param query     查询
     * @param entity    实体
     * @param sort      排序
     * @param order     顺序
     * @param pageNum   页码
     * @param pageSize  页面大小
     * @param highLight 高亮串
     * @return {@link PageVO<Object> }
     * @throws Exception 异常
     */
    public static PageVO<Object> searchPageHighlight(String query, Class<?> entity, String sort, String order, int pageNum, int pageSize, String highLight) throws Exception {
        return searchPageHighlight(null, query, entity, sort, order, pageNum, pageSize, highLight);
    }

    /**
     * 方法说明：solr 高亮分页查询
     *
     * @param coreName  存储的集合名称
     * @param query     查询
     * @param entity    实体
     * @param sort      排序
     * @param order     顺序
     * @param pageNum   页码
     * @param pageSize  页面大小
     * @param highLight 高亮串
     * @return {@link PageVO<Object> }
     * @throws Exception 异常
     */
    public static PageVO<Object> searchPageHighlight(String coreName, String query, Class<?> entity, String sort, String order, int pageNum, int pageSize, String highLight) throws Exception {
        List<Object> list = new ArrayList<>();
        // 构建查询对象
        SolrQuery solrQuery = buildQuery(query, entity);
        // 设置排序
        setSort(sort, order, solrQuery);
        // 设置查询页码和记录数
        if (pageNum < 0) {
            pageNum = MIN_PAGE_NUM;
        }
        if (pageSize < 0) {
            pageSize = MIN_PAGE_SIZE;
        } else if (pageSize > MAX_PAGE_SIZE) {
            pageSize = MAX_PAGE_SIZE;
        }
        solrQuery.setStart((pageNum - 1) * pageSize);
        solrQuery.setRows(pageSize);
        // 高亮查询
        setHighLight(highLight, solrQuery);
        // solr 检索
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        QueryResponse response = SOLR_CLIENT.query(coreName, solrQuery);
        SolrDocumentList docs = response.getResults();
        // 获取高亮结果
        buildHighLightResultList(entity, highLight, list, response, docs);
        return PageVO.of(pageNum, pageSize, (int)docs.getNumFound(), list);
    }

    /**
     * 方法说明：设置高亮
     *
     * @param highLight 高亮字符串
     * @param solrQuery solr查询
     */
    private static void setHighLight(String highLight, SolrQuery solrQuery) {
        if (StringUtils.isNotBlank(highLight.trim())) {
            // 开启高亮提示
            solrQuery.setHighlight(true);
            // 设置高亮字段
            solrQuery.addHighlightField(highLight.trim());
            // 设置高亮前缀
            solrQuery.setHighlightSimplePre("<font color='red'>");
            // 设置高亮后缀
            solrQuery.setHighlightSimplePost("</font>");
        }
    }

    /**
     * 方法说明：搜索查询
     *
     * @param coreName  存储集合名称
     * @param solrQuery solr查询对象
     * @return {@link SolrDocumentList }
     * @throws SolrServerException solr服务器异常
     * @throws IOException         IO异常
     */
    private static SolrDocumentList doSearch(String coreName, SolrQuery solrQuery) throws SolrServerException, IOException {
        if (StringUtils.isBlank(coreName)) {
            coreName = SOLR_CORE;
        }
        QueryResponse response = SOLR_CLIENT.query(coreName, solrQuery);
        return response.getResults();
    }

    /**
     * 方法说明：设置排序
     *
     * @param sort      排序
     * @param order     订单
     * @param solrQuery solr查询
     */
    private static void setSort(String sort, String order, SolrQuery solrQuery) {
        // 设置排序
        if (StringUtils.isNotBlank(sort)) {
            if (SolrUtil.DESC.equalsIgnoreCase(order)) {
                solrQuery.setSort(sort, SolrQuery.ORDER.desc);
            }
            if (SolrUtil.ASC.equalsIgnoreCase(order)) {
                solrQuery.setSort(sort, SolrQuery.ORDER.asc);
            }
        }
    }

    /**
     * 方法说明：根据查询串和目标对象字节码构建查询对象
     *
     * @param query  查询串
     * @param entity 实体字节码
     * @return {@link SolrQuery }
     */
    private static SolrQuery buildQuery(String query, Class<?> entity) {
        SolrQuery solrQuery = new SolrQuery();
        // 设置查询条件
        solrQuery.setQuery(query);
        // 设置查询属性
        StringBuilder builder = new StringBuilder();
        Field[] fields = ReflectionUtil.getAllFields(entity);
        for (Field field : fields) {
            builder.append(field.getName()).append(",");
        }
        String field = builder.toString();
        field = field.substring(0, field.length() - 1);
        solrQuery.setFields(field);
        return solrQuery;
    }

    /**
     * 方法说明：构建solr结果列表
     *
     * @param entity 实体
     * @param list   列表
     * @param docs   文档
     * @throws InstantiationException 实例化异常
     * @throws IllegalAccessException 非法访问异常
     * @return 返回结果集
     */
    private static List<Object> buildSolrResultList(Class<?> entity, List<Object> list, SolrDocumentList docs) throws InstantiationException, IllegalAccessException {
        if (CollectionUtils.isEmpty(docs)) {
            return list;
        }
        for (SolrDocument doc : docs) {
            Object obj = entity.newInstance();
            ArrayList<Field> searchFields = new ArrayList<>(Arrays.asList(ReflectionUtil.getAllFields(obj.getClass())));
            for (Field searchField : searchFields) {
                String propertyName = searchField.getName();
                String propertyValue = (String) doc.getFieldValue(propertyName);
                Class<?> propertyClass = searchField.getType();
                if (propertyClass.equals(Integer.class)) {
                    Integer value = Integer.valueOf(propertyValue);
                    ReflectionUtil.invokeSetterMethod(obj, propertyName, value);
                } else {
                    ReflectionUtil.invokeSetterMethod(obj, propertyName, propertyValue);
                }
            }
            list.add(obj);
        }
        return list;
    }

    /**
     * 方法说明：构建高亮结果列表集
     *
     * @param entity    待返回的结果列表实体对象字节码
     * @param highLight 高亮参数串
     * @param list      待返回的结果列表
     * @param response  solr查询响应对象
     * @param docs      solr查询到的doc集
     * @throws InstantiationException 实例化异常
     * @throws IllegalAccessException 非法访问异常
     */
    private static void buildHighLightResultList(Class<?> entity, String highLight, List<Object> list, QueryResponse response, SolrDocumentList docs) throws InstantiationException, IllegalAccessException {
        if (CollectionUtils.isEmpty(docs)) {
            return;
        }
        Map<String, Map<String, List<String>>> highLightMap = null;
        if (StringUtils.isNotBlank(highLight.trim())) {
            highLightMap = response.getHighlighting();
        }
        Map<String, List<String>> map;
        for (SolrDocument doc : docs) {
            Object obj = entity.newInstance();
            ArrayList<Field> searchFields = new ArrayList<>(Arrays.asList(ReflectionUtil.getAllFields(obj.getClass())));
            for (Field searchField : searchFields) {
                String propertyName = searchField.getName();
                String propertyValue = (String) doc.getFieldValue(propertyName);
                Class<?> propertyClass = searchField.getType();
                // 高亮字段存在
                if (StringUtils.isNotBlank(highLight.trim())) {
                    // 判断反射字段是否与高亮字段相等
                    if (propertyName.equalsIgnoreCase(highLight.trim()) && highLightMap != null) {
                        // 反射字段类型为Integer 不高亮显示
                        if (propertyClass.equals(Integer.class)) {
                            Integer value = Integer.valueOf(propertyValue);
                            ReflectionUtil.invokeSetterMethod(obj, propertyName, value);
                        } else {
                            // 放射字段类型为其他时，高亮显示
                            map = highLightMap.get((String) doc.getFieldValue(SOLR_ID));
                            String value = map.get(propertyName).get(0);
                            ReflectionUtil.invokeSetterMethod(obj, propertyName, value);
                        }
                    } else {
                        // 判断反射字段是否与高亮字段不相等
                        if (propertyClass.equals(Integer.class)) {
                            Integer value = Integer.valueOf(propertyValue);
                            ReflectionUtil.invokeSetterMethod(obj, propertyName, value);
                        } else {
                            ReflectionUtil.invokeSetterMethod(obj, propertyName, propertyValue);
                        }
                    }
                } else {
                    // 高亮字段不存在
                    if (propertyClass.equals(Integer.class)) {
                        Integer value = Integer.valueOf(propertyValue);
                        ReflectionUtil.invokeSetterMethod(obj, propertyName, value);
                    } else {
                        ReflectionUtil.invokeSetterMethod(obj, propertyName, propertyValue);
                    }
                }
            }
            list.add(obj);
        }
    }
}
